/**
 * @author  Thomas Lehmann
 * @file    CodeView.js
 *
 * Copyright (c) 2013 Thomas Lehmann
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies
 * or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
var log;
var Class;
var Document;
var StringTools;
var DefaultScanner;
var StylesheetsFactory;

// https://gist.github.com/aemkei/1180489
function pad(a,b){return(1e15+a+"").slice(-b)}

// this is used when developing this tool with node
if (typeof require == 'function') {
    var ClassTools = require("./ClassTools.js");
    log      = ClassTools.log;
    Class    = ClassTools.Class;
    Document = ClassTools.Document;

    StringTools        = require("./StringTools.js").StringTools;
    DefaultScanner     = require("./DefaultScanner.js").DefaultScanner;
    StylesheetsFactory = require("./Stylesheets.js").StylesheetsFactory;
}

var Token = Class({
    initialize: function(id, code, line) {
        this.id   = id;
        this.code = code;
        this.line = line;
    }
});

var Document = Class({
    initialize: function() {
    },

    write: function (text) {
        if (typeof(document) != "undefined" && typeof(document.write) == 'function') {
            document.write(text);
        } else {
            log(text);
        }
    },

    getElementsByTagsName: function(name) {
        if (typeof(document) != "undefined" && typeof(document.getElementsByTagName) == 'function') {
            return document.getElementsByTagName(name);
        }
        return new Array();
    }
});

var CodeView = Class({
    initialize: function() {
        this.document           = new Document();
        this.tokens             = new Array();
        this.line               = 0;
        this.currentStyle       = "default";
        this.currentLineNumbers = false;
    },

    addToken: function(id, code) {
        this.tokens.push(new Token(id, code, this.line));
        if (code == "\n") {
            this.line += 1;
        }
    },

    scan: function(text) {
        this.tokens = new Array();

        var handler    = new DefaultScanner(this.addToken.bind(this));
        var processors = handler.scannerFunctions();

        for (var index = 0; index < text.length; ++index) {
            for (var processorIx = 0; processorIx < processors.length; ++processorIx) {
                var process = processors[processorIx].bind(handler);
                var newIndex = process(text, index);
                if (newIndex > index) {
                    index = newIndex-1;
                    break;
                }
            }
        }

        this.currentStyle       = handler.style;
        this.currentLineNumbers = handler.lineNumbers;
    },

    /**
        Helper function for development only.
     */
    dump: function() {
        for (var index = 0; index < this.tokens.length; ++index) {
            log("id='"    + this.tokens[index].id   + "'" +
                " line='" + this.tokens[index].line + "'" +
                " code='" + this.tokens[index].code + "'");
        }
    },

    highlightElement: function(element) {
        styleSheets = StylesheetsFactory.createInstance(this.currentStyle)

        var newContent = "";
        var lineNumber = 1;
        var lineNumberStyle = styleSheets.getStyle("line-number");

        if (this.currentLineNumbers) {
            newContent += "<span style=\"" + lineNumberStyle + "\">" + pad(lineNumber, 3) + " </span>" ;
            lineNumber += 1;
        }

        for (var index = 0; index < this.tokens.length; ++index) {
            var token = this.tokens[index];
            var style = styleSheets.getStyle(token.id);

            var subTokens = new Array(token.code);

            if (token.id == "block-comment") {
                subTokens = token.code.split("<br/>");
            }

            for (var subIndex = 0; subIndex < subTokens.length; ++subIndex) {
                if (style == undefined) {
                    newContent += subTokens[subIndex];
                } else {
                    newContent += "<span style=\"" + style + "\">" +
                                   subTokens[subIndex] +  "</span>";
                }

                if (token.id == 'block-comment' && subIndex+1 < subTokens.length) {
                    newContent += "<br/>";
                }

                if (this.currentLineNumbers && (token.id == "new-line" || token.id == 'block-comment')) {
                    newContent += "<span style=\"" + lineNumberStyle + "\">" + pad(lineNumber, 3) + " </span>" ;
                    lineNumber += 1;
                }
            }
        }
        newContent += "";
        element.innerHTML = newContent;

        var preStyle = styleSheets.getStyle("pre");
        if (preStyle.length > 0) {
            element.setAttribute("style", preStyle);
        }
    },

    process: function(tagName) {
        var elements = this.document.getElementsByTagsName(tagName);
        for (index = 0; index < elements.length; ++index) {
            // removes all tags
            code = elements[index].innerHTML.replace(/(<([^>]+)>)/ig, "");
            // replaces all HTML representations for '<', '>' and '&'
            code = code.replace(/&lt;/g, '<');
            code = code.replace(/&gt;/g, '>');
            code = code.replace(/&amp;/g, '&');
            // scans the tokens
            this.scan(code);
            // replaces HTML element with highlighted code
            this.highlightElement(elements[index]);
        }
    }
});

function highlight(tagName) {
    var instance = new CodeView();
    instance.process(tagName);
}
